/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2008 Philippe Durand <draekz@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

using Gtk;

using Mono.Addins;

using Galaxium.Client;
using Galaxium.Core;
using Galaxium.Gui.GtkGui;
using Galaxium.Protocol;

using Anculus.Core;
using Anculus.Gui;

namespace Galaxium.Client.GtkGui
{
	public static class GtkActivityUtility
	{
		public static event EventHandler ActivityQueued;
		
		const string _preProcessorPath = "/Galaxium/Activities/PreProcessors";
		const string _processorPath    = "/Galaxium/Activities/Processors";
		
		static Dictionary<IContact, Queue<IActivity>> _queues;
		static IConfigurationSection _config = Configuration.Conversation.Section;
		
		/// <value>
		/// True if any activities are currently queued
		/// </value>
		public static bool Pending
		{
			get
			{
				lock (_queues)
					return _queues.Count > 0;
			}
		}
		
		static List<KeyValuePair<Type, MethodInfo>> _preProcessors;
		static List<KeyValuePair<Type, MethodInfo>> _processors;
		
		/// <summary>
		/// Initialize GtkActivityUtility. Must be called before use
		/// </summary>
		public static void Initialize ()
		{
			Log.Debug ("Initializing gtk activity utility...");
			
			_preProcessors = new List<KeyValuePair<Type, MethodInfo>> ();
			_processors = new List<KeyValuePair<Type, MethodInfo>> ();
			
			_queues = new Dictionary<IContact, Queue<IActivity>> ();
			
			LoadProcessors (ref _preProcessors, _preProcessorPath);
			LoadProcessors (ref _processors, _processorPath);
			
			AddinManager.ExtensionChanged += OnExtensionChanged;
		}
		
		/// <summary>
		/// Load all processors at the given extension point into the given list
		/// </summary>
		/// <param name="list">
		/// A <see cref="List`1"/> which will contain the processors
		/// </param>
		/// <param name="extensionPoint">
		/// A <see cref="System.String"/> containing the extension point to load from
		/// </param>
		static void LoadProcessors (ref List<KeyValuePair<Type, MethodInfo>> list, string extensionPoint)
		{
			list.Clear ();
			
			//Log.Debug ("Loading processors from {0}", extensionPoint);
			
			foreach (ActivityProcessorExtension node in AddinUtility.GetExtensionNodes (extensionPoint))
				list.Add (new KeyValuePair<Type, MethodInfo> (node.ActivityType, node.CallMethod));
		}
		
		static void OnExtensionChanged (object o, ExtensionEventArgs args)
		{
			if (args.PathChanged (_preProcessorPath))
				LoadProcessors (ref _preProcessors, _preProcessorPath);
			
			if (args.PathChanged (_processorPath))
				LoadProcessors (ref _processors, _processorPath);
		}
		
		/// <summary>
		/// Called by the main ActivityUtility when an activity is emitted
		/// </summary>
		/// <param name="sender">
		/// The <see cref="System.Object"/> which generated the activity
		/// </param>
		/// <param name="activity">
		/// The emmitted <see cref="IActivity"/>
		/// </param>
		public static void OnActivity (object sender, IActivity activity)
		{
			Process (activity, _preProcessors);
			
			if (activity.Handled)
			{
				Log.Debug ("Activity {0} handled by a preprocessor, dropping it", activity);
				return;
			}
			
			if (activity.CanQueue && (activity.Entity != null))
			{
				IConversation conv = activity.Session.Conversations.GetConversation (activity.Entity as IContact);
				
				if (conv != null && WindowUtility<Widget>.GetWindow (conv) != null)
				{
					Process (activity, _processors);
				}
				else
				{
					if (_config.GetBool (Configuration.Conversation.QueueAlways.Name, Configuration.Conversation.QueueAlways.Default))
						QueueActivity (activity);
					else if (_config.GetBool (Configuration.Conversation.QueueWhenAway.Name, Configuration.Conversation.QueueWhenAway.Default) && activity.Session.Account.Presence.BasePresence == BasePresence.Away)
						QueueActivity (activity);
					else
						Process (activity, _processors);
				}
			}
			else
				Process (activity, _processors);
		}
		
		/// <summary>
		/// Get the first activity from the first queue without removing or processing it
		/// </summary>
		/// <returns>
		/// The first <see cref="IActivity"/> from the first queue
		/// </returns>
		public static IActivity PeekQueueItem ()
		{
			lock (_queues)
			{
				foreach (IContact contact in _queues.Keys)
					return _queues[contact].Peek ();
			}
			
			return null;
		}
		
		/// <summary>
		/// Enqueue an activity for later processing
		/// </summary>
		/// <param name="activity">
		/// The <see cref="IActivity"/> to enqueue
		/// </param>
		static void QueueActivity (IActivity activity)
		{
			ThrowUtility.ThrowIfFalse ("entity not an IContact", activity.Entity is IContact);
			
			lock (_queues)
			{
				if (!_queues.ContainsKey (activity.Entity as IContact))
					_queues.Add (activity.Entity as IContact, new Queue<IActivity> ());
				
				_queues[activity.Entity as IContact].Enqueue (activity);
			}
			
			if (ActivityQueued != null)
				ActivityQueued (activity, new EventArgs ());
		}
		
		/// <summary>
		/// Process one queue (all queued activities for one contact)
		/// </summary>
		public static void ProcessQueue ()
		{
			lock (_queues)
			{
				if (_queues.Count == 0)
					return;
				
				foreach (IContact contact in _queues.Keys)
				{
					Queue<IActivity> queue = _queues[contact];
					
					while (queue.Count > 0)
						Process (queue.Dequeue (), _processors);
					
					_queues.Remove (contact);
					
					// We only want to process one queue here
					break;
				}
			}
		}
		
		/// <summary>
		/// Invoke the appropriate processors from a given set, for the activity provided
		/// </summary>
		/// <param name="activity">
		/// The <see cref="IActivity"/> to process
		/// </param>
		/// <param name="processors">
		/// A set of processors to invoke if they accept the given activity's type
		/// </param>
		static void Process (IActivity activity, IEnumerable<KeyValuePair<Type, MethodInfo>> processors)
		{
			ThrowUtility.ThrowIfNull ("activity", activity);
			ThrowUtility.ThrowIfNull ("processors", processors);
			
			foreach (KeyValuePair<Type, MethodInfo> processor in processors)
			{
				// Execute the processor method if it handles the activities type, an
				// ancestor of the activities type or an interface implemented by the
				// activities type
				
				if ((processor.Key == activity.GetType ()) || activity.GetType ().IsSubclassOf (processor.Key) ||
				    (processor.Key.IsInterface && (activity.GetType ().GetInterface (processor.Key.Name) != null)))
				{
					if (processor.Value != null)
						processor.Value.Invoke (null, new object[] { null, activity });
					else
						Log.Error ("Method information not found! Are you sure the addin specifies the proper method?");
				}
			}
		}
	}
}